﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Windows;

namespace WrWpf.Windows.Input
{
    /// <summary>
    /// This abstract class is a RoutedCommand which allows its subclasses to provide default logic 
    /// for determining if they can execute and how to execute.  To enable the default logic to be 
    /// used, set the IsCommandSink attached property to true on the root element of the element tree 
    /// which uses one or more SmartRoutedCommand subclasses.
    /// </summary>
    /// <example>
    /// /// <summary>
    /// /// A routed command which knows how to open a Web page in a browser.
    /// /// </summary>
    /// public class OpenWebPageCommand : SmartRoutedCommand
    /// {
    ///     /// <summary>
    ///     /// Singleton instance of this class.
    ///     /// </summary>
    ///     public static readonly ICommand Instance = new OpenWebPageCommand();
    /// 
    ///     private OpenWebPageCommand() { }
    /// 
    ///     protected override bool CanExecuteCore(object parameter)
    ///     {
    ///         string uri = parameter as string;
    ///         if (uri == null)
    ///         return false;
    /// 
    ///         bool isUriValid = Uri.IsWellFormedUriString(uri, UriKind.Absolute);
    ///         bool haveConnection = NetworkInterface.GetIsNetworkAvailable();
    /// 
    ///         return isUriValid && haveConnection;
    ///     }
    ///     
    ///     protected override void ExecuteCore(object parameter)
    ///     {
    ///         string uri = parameter as string;
    ///         Process.Start(uri);
    ///     }
    /// }
    /// 
    /// <StackPanel.CommandBindings>
    ///     <CommandBinding 
    ///     Command="{x:Static local:OpenWebPageCommand.Instance}" 
    ///     CanExecute="OnCanCmdExecute" 
    ///     Executed="OnCmdExecuted" />
    /// </StackPanel.CommandBindings>
    /// 
    /// void OnCanCmdExecute(object sender, CanExecuteRoutedEventArgs e)
    /// {
    ///     // Only execute this Window's custom command logic if the CheckBox is checked.
    ///     Otherwise let the default logic of OpenWebPageCommand execute.
    ///     bool useCustomBrowser = this.chkUseCustomBrowser.IsChecked.GetValueOrDefault();
    ///     
    ///     if(useCustomBrowser)
    ///     {
    ///         // Assume we have an internet connection, just to keep this demo simple.  By marking
    ///         //CanExecute as true, this element will be asked to execute the command.
    ///         e.CanExecute = true;
    ///     }
    /// }
    /// </example>
    public abstract class SmartRoutedCommand : RoutedCommand
    {
        #region IsCommandSink
        /// <summary>
        /// Set IsCommandSink to true on the root element in the element tree to enables your 
        /// SmartRoutedCommand subclasses to perform their default execution logic when routed command 
        /// execution notifications are unhandled by the element tree.
        /// </summary>
        public static readonly DependencyProperty IsCommandSinkProperty = DependencyProperty.RegisterAttached(
          "IsCommandSink",
          typeof(bool),
          typeof(SmartRoutedCommand),
          new UIPropertyMetadata(false, OnIsCommandSinkChanged));

        /// <summary>
        /// Gets the value of IsCommandSink.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static bool GetIsCommandSink(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsCommandSinkProperty);
        }

        /// <summary>
        /// Sets the value to IsCommandSink.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">if set to <c>true</c> [value].</param>
        public static void SetIsCommandSink(DependencyObject obj, bool value)
        {
            obj.SetValue(IsCommandSinkProperty, value);
        }

        /// <summary>
        /// Invoked when the IsCommandSink attached property is set on an element.
        /// </summary>
        /// <param name="obj">The element on which the property was set.</param>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        static void OnIsCommandSinkChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (obj is UIElement)
            {
                UIElement element = obj as UIElement;

                if ((bool)e.NewValue)
                {
                    CommandManager.AddCanExecuteHandler(element, OnCanExecute);
                    CommandManager.AddExecutedHandler(element, OnExecuted);
                }
                else
                {
                    CommandManager.RemoveCanExecuteHandler(element, OnCanExecute);
                    CommandManager.RemoveExecutedHandler(element, OnExecuted);
                }
            }
            else
                throw new ArgumentException("Target object must be a UIElement.", "obj");
        }
        #endregion

        #region Static callbacks
        /// <summary>
        /// Called when [can execute].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        static void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            SmartRoutedCommand cmd = e.Command as SmartRoutedCommand;
            if (cmd != null)
            {
                e.CanExecute = cmd.CanExecuteCore(e.Parameter);
            }
        }

        /// <summary>
        /// Called when [executed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        static void OnExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            SmartRoutedCommand cmd = e.Command as SmartRoutedCommand;
            if (cmd != null)
            {
                cmd.ExecuteCore(e.Parameter);
                e.Handled = true;
            }
        }
        #endregion

        #region Abstract methods
        /// <summary>
        /// Child classes override this method to provide logic which determines if the command can execute.
        /// This method will only be invoked if no element in the tree indicated that it can execute the 
        /// command.
        /// </summary>
        /// <param name="parameter">The command parameter (optional).</param>
        /// <returns>
        /// 	<c>true</c> if the command can be executed, else <c>false</c>.
        /// </returns>
        protected abstract bool CanExecuteCore(object parameter);

        /// <summary>
        /// Child classes override this method to provide default execution logic.  This method will only be 
        /// invoked if CanExecuteCore returns true.
        /// </summary>
        /// <param name="parameter">The command parameter (optional).</param>
        protected abstract void ExecuteCore(object parameter);
        #endregion
    }
}
